#include "networkmodule.h"
#include "drunkscenemodel.h"
#include <QNetworkAccessManager>
#include <QUrl>
#include <QNetworkReply>
#include <QTimer>
#include <QtXml>


NetworkModule::NetworkModule(QString url,int port,QObject *parent) : QObject(parent){
    networkAccessManager = new QNetworkAccessManager(this);
    theModel = 0;

    serverUrl = new QUrl(url);
    serverUrl->setPort(port);
    loopTimer = new QTimer(this);

    connect(networkAccessManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(processReplies(QNetworkReply*)));
}

/*Functions that switch the type of the requests based on the state the game is*/
void NetworkModule::switchToActiveChallengeLoop(){
    processQueue.clear();

    disconnect(loopTimer,SIGNAL(timeout()),this,SLOT(challengeRoomRequests()));
    disconnect(loopTimer,SIGNAL(timeout()),this,SLOT(gameplayRequests()));

    connect(loopTimer,SIGNAL(timeout()),this,SLOT(activeChallengeRequests()));
}

void NetworkModule::switchToGameplayLoop(){
    processQueue.clear();

    disconnect(loopTimer,SIGNAL(timeout()),this,SLOT(activeChallengeRequests()));
    disconnect(loopTimer,SIGNAL(timeout()),this,SLOT(challengeRoomRequests()));

    connect(loopTimer,SIGNAL(timeout()),this,SLOT(gameplayRequests()));
}

void NetworkModule::switchToChallengeRoomLoop(){
    processQueue.clear();

    disconnect(loopTimer,SIGNAL(timeout()),this,SLOT(activeChallengeRequests()));
    disconnect(loopTimer,SIGNAL(timeout()),this,SLOT(gameplayRequests()));

    connect(loopTimer,SIGNAL(timeout()),this,SLOT(challengeRoomRequests()));
}
/*group requests for each state*/
void NetworkModule::challengeRoomRequests(){
    playersAvailableRequest();
    checkForPendingChallengesRequest();
}

void NetworkModule::activeChallengeRequests(){
    checkActiveChallengeStatusRequest();
}

void NetworkModule::gameplayRequests(){
    gameStatusUpdateRequest();
}

/*single requests*/
void NetworkModule::attemptLogin(QString username, QString password){
    QUrl url(*serverUrl);
    url.setPath("/content/login");

    QString postParams="username="+username+"&password="+password;

    QNetworkRequest request(url);
    processQueue.append(networkAccessManager->post(request,postParams.toStdString().c_str()));
}

void NetworkModule::logoutRequest(){
    QUrl url(*serverUrl);
    url.setPath("/content/logout");

    QNetworkRequest request(url);
    processQueue.append(networkAccessManager->get(request));
}

void NetworkModule::playersAvailableRequest(){
    QUrl url(*serverUrl);
    url.setPath("/content/getAvailablePlayers");

    QNetworkRequest request(url);
    processQueue.append(networkAccessManager->get(request));
}

void NetworkModule::checkForPendingChallengesRequest(){
    QUrl url(*serverUrl);
    url.setPath("/content/checkForPendingChallenges");

    QNetworkRequest request(url);
    processQueue.append(networkAccessManager->get(request));
}

void NetworkModule::acceptChallengeBy(QString player){
    processQueue.clear();
    loopTimer->stop();

    QUrl url(*serverUrl);
    QString params="/player/"+player;

    url.setPath("/content/acceptChallenge"+params);
    QNetworkRequest request(url);

    processQueue.append(networkAccessManager->get(request));
}

void NetworkModule::declineChallengeBy(QString player){
    processQueue.clear();
    QUrl url(*serverUrl);
    QString params="/player/"+player;

    url.setPath("/content/declineChallenge"+params);
    QNetworkRequest request(url);

    processQueue.append(networkAccessManager->get(request));
}

void NetworkModule::challengePlayer(QString player){
    switchToActiveChallengeLoop();
    QUrl url(*serverUrl);
    url.setPath("/content/challengePlayer/player/"+player);

    QNetworkRequest request(url);
    processQueue.append(networkAccessManager->get(request));
}

void NetworkModule::checkActiveChallengeStatusRequest(){
    QUrl url(*serverUrl);
    QString params="/opponent/"+opponent;

    url.setPath("/content/checkChallengeStatus"+params);
    QNetworkRequest request(url);

    processQueue.append(networkAccessManager->get(request));
}

void NetworkModule::cancelActiveChallengeRequest(){
    switchToChallengeRoomLoop();
    QUrl url(*serverUrl);
    QString params="/opponent/"+opponent;

    url.setPath("/content/cancelChallengeToPlayer"+params);
    QNetworkRequest request(url);

    processQueue.append(networkAccessManager->get(request));
}

void NetworkModule::fetchMapRequest(){
    QUrl url(*serverUrl);

    url.setPath("/content/fetchMap");
    QNetworkRequest request(url);

    processQueue.append(networkAccessManager->get(request));
}

void NetworkModule::gameStatusUpdateRequest(){
    QUrl url(*serverUrl);
    QString params = "/position/"+QString::number(theModel->getYPosition())+"/time/"+
            QString::number(theModel->getTotalTimeToFinish());

    url.setPath("/content/gameStatusUpdate"+params);
    QNetworkRequest request(url);

    processQueue.append(networkAccessManager->get(request));
}

/*helper functions for outside use*/
void NetworkModule::startRequestLoop(){
    switchToChallengeRoomLoop();
    playersAvailableRequest();

    loopTimer->start(1000);
}

void NetworkModule::stopRequestLoop(){
    loopTimer->stop();
}

void NetworkModule::dropPendingReplies(){
    processQueue.clear();
}


/*provides an ordered way to deal with the request replies*/
void NetworkModule::processReplies(QNetworkReply *reply){

    while(!processQueue.isEmpty() && processQueue.first()->isFinished()){
        if(processQueue.first()->error()!=0){
            qDebug()<<processQueue.first()->error();
            loopTimer->stop();
            processQueue.clear();
            emit connectionError();
            return;
        }

        parseReply(processQueue.takeFirst());
    }

}

/*The parser for the XML responses*/
void NetworkModule::parseReply(QNetworkReply *reply){
    QString error;
       int line;
       int column;

       QDomDocument responseDocument;

       if(!responseDocument.setContent(reply,true,&error,&line,&column)){
           return;
       }

       QDomElement root = responseDocument.documentElement();

       if(root.tagName()=="login"){
           QDomElement resultValue = root.toElement();
           if(resultValue.text()=="OK")
               emit loginSuccess();
           else if(resultValue.text()=="FAILED")
               emit loginFailure();
       }

       else if(root.tagName()=="logout"){
           QDomElement resultValue = root.toElement();
           if(resultValue.text()=="OK"){
               loopTimer->stop();
               processQueue.clear();
               emit logoutSuccess();
           }
       }

       else if(root.tagName()=="player_list"){
           QDomNode player = root.firstChild();
           QList<QString> players;
           while(!player.isNull()){
               players<<player.toElement().text().trimmed();
               player=player.nextSibling();
           }
           emit playersAvailableForChallenge(players);
       }

       else if(root.tagName()=="pending_challenge"){
           QDomElement resultValue = root.toElement();
           if(resultValue.text().trimmed()!="")
               emit pendingChallengeFrom(resultValue.text());
           else
               emit noPendingChallenges();
       }

       else if(root.tagName()=="accept_challenge"){
           QDomElement resultValue = root.toElement();
           if(resultValue.text().trimmed()=="OK"){
               processQueue.clear();
               fetchMapRequest();
           }
       }

       else if(root.tagName()=="challenge_delivery"){
           QDomElement status = root.firstChild().toElement();
           QDomElement playerElement = root.firstChild().nextSibling().toElement();

           if(status.text().trimmed() == "OK"){
               opponent = playerElement.text().trimmed();
               emit challengeDeliveredToPlayer(playerElement.text().trimmed());
           }
           else{
               emit challengeDeliveryToPlayerFailed(playerElement.text().trimmed());
           }
       }

       else if(root.tagName()=="challenge_status"){
           QDomElement resultValue = root.toElement();

           if(resultValue.text().trimmed()=="OK"){
               processQueue.clear();
               fetchMapRequest();
           }

           else if(resultValue.text().trimmed()=="DECLINE"){
               switchToChallengeRoomLoop();
               emit challengeDeclined();
           }
       }

       else if(root.tagName()=="map_response"){

       bool ok;

       float width;
       float height;
       float movementSpeed;
       int falldownPenalty;
       float diffFactor;

       int randomSeed=0;

       QDomNode map=root.firstChild();
       QDomNode objects=map.nextSibling();

       if(map.toElement().tagName()!="map" && objects.toElement().tagName()!="object_list")
           return;

       QDomNode mapConfig=map.firstChild();
       QDomNode object=objects.firstChild();


       while(!mapConfig.isNull()){//get map configuration
           QDomElement element=mapConfig.toElement();
           if(element.tagName()=="width"){
               if(element.text()==""){
                   theModel=0;
                   randomSeed=0;
                   return;
               }

               else
                   width=element.text().toDouble(&ok);

           }
           else if(element.tagName()=="height"){
               if(element.text()==""){
                   theModel=0;
                   randomSeed=0;
                   return;
               }
               else
                   height=element.text().toDouble(&ok);
           }
           else if(element.tagName()=="movement_speed"){
               if(element.text()==""){
                   theModel=0;
                   randomSeed=0;
                   return;
               }
               else
                   movementSpeed=element.text().toDouble(&ok);
           }
           else if(element.tagName()=="falldown_penalty_ms"){
               if(element.text()==""){
                   theModel=0;
                   randomSeed=0;
                   return;
               }
               else
                   falldownPenalty=element.text().toInt(&ok);
           }
           else if(element.tagName()=="difficulty_factor"){
               if(element.text()==""){
                   theModel=0;
                   randomSeed=0;
                   return;
               }
               else
                   diffFactor=element.text().toDouble(&ok);
           }
           mapConfig=mapConfig.nextSibling();
       }

       theModel=new DrunkSceneModel(100,width,height,new Noise(randomSeed),movementSpeed,falldownPenalty);
       theModel->setNavigationDifficulty(diffFactor);

       while(!object.isNull()){//get all objects
           QDomNode objectConfig=object.firstChild();

           QString resourceId;
           float xPos;
           float yPos;
           float objWidth;
           int effectDurationInMs;

           float gravityFactor;
           float randomnessFactor;
           float walkingSpeedFactor;

           while(!objectConfig.isNull()){
               QDomElement element=objectConfig.toElement();

               if(element.tagName()=="resource_id"){
                   resourceId=element.text();
               }
               else if(element.tagName()=="x_position"){
                   xPos=element.text().toDouble(&ok);
               }
               else if(element.tagName()=="y_position"){
                   yPos=element.text().toDouble(&ok);
               }
               else if(element.tagName()=="object_width"){
                   objWidth=element.text().toDouble(&ok);
               }
               else if(element.tagName()=="effect_duration_ms"){
                   effectDurationInMs=element.text().toInt(&ok);
               }
               else if(element.tagName()=="gravity_factor"){
                   gravityFactor=element.text().toDouble(&ok);
               }
               else if(element.tagName()=="randomness_factor"){
                   randomnessFactor=element.text().toDouble(&ok);
               }
               else if(element.tagName()=="walking_speed_factor"){
                   walkingSpeedFactor=element.text().toDouble(&ok);
               }
               objectConfig=objectConfig.nextSibling();
           }

           theModel->putObject(new SceneObject(gravityFactor,randomnessFactor,walkingSpeedFactor,effectDurationInMs,xPos,yPos,objWidth,resourceId));
           object=object.nextSibling();
       }

       loopTimer->stop();
       switchToGameplayLoop();
       emit readyForGameState();
       }

       else if(root.tagName()=="game_status"){
           QDomElement positionElement = root.firstChild().toElement();
           QDomElement resultElement = root.firstChild().nextSibling().toElement();
           QDomElement timeElement = root.firstChild().nextSibling().nextSibling().toElement();

           bool ok = true;
           float position = positionElement.text().trimmed().toFloat(&ok);
           int time = timeElement.text().trimmed().toInt(&ok);

           emit opponentAt(position);

           if(resultElement.text().trimmed()=="WIN"){
               loopTimer->stop();
               emit win(true,time);
           }
           else if(resultElement.text().trimmed()=="LOSE"){
               loopTimer->stop();
               emit win(false,time);
           }
       }
}
